from threading import Thread
import exceptions

# TODO: complete the documentation for AbstractMapper

class AbstractMapper(Thread):
    """this class is to provide framework driven functions for user defined maps
    so that the user is not bothered with administrative tasks like writing tuples 
    that are emitted by maps.
    
    The requirements on the user class inheriting from this one is that the user
    class should:
    1. define the map(self, filename, filecontents) function which would use
        emit(self, key, list_of_values) to register values mapped
    2. define in __init__ the list of keys that are going to be mapped in 
        self.list_of_keys. If the user doesn't know before hand, its left
        undefined
    """
    
    WITH_COMBINER = 'C'
    WITHOUT_COMBINER = 'WC'
    
    
    def __init__(self, filename, filecontents, key_value_dict, combinable=WITHOUT_COMBINER):
        """
        """
        
        self.key_value_dict = key_value_dict
        self.__counter = 0
        
        self.list_of_keys = []
        
        self.filename = filename
        self.filecontents = filecontents
        
        self.combinable = combinable
        
        Thread.__init__(self)
        
        
    def map(self, filename, filecontents):
        """this is the prototype for the user defined map function
        """
        
        raise exceptions.NotImplementedError('Base class doesn\'t implement the Mapper\'s Map')
    
        
    def combine(self, key, list_of_values):
        """this is the prototype for the user defined combiner function
        """
        
        raise exceptions.NotImplementedError('Base class doesn\'t implement the Mapper\'s Combine')
    
        
    def run(self):
        """Called by the thread's start function
        """
        
        self.map(self.filename, self.filecontents)
        
        # if the function has a combiner provided by the user
        if self.combinable == self.WITH_COMBINER:
            # iterate through all keys
            for key in self.key_value_dict:
                # call the combiner and adjust the dictionary
                new_key_value = self.combine(key, self.key_value_dict[key])
                self.key_value_dict[key] = new_key_value
                
        
    def emit(self, key, list_of_values):
        """this functions will be used by the user map to emit its output, key, value pairs
        """
        
        self.__counter += 1
        """
        if self.__counter % 10000 == 0:
            print 'Values mapped:', self.__counter
        """
        if key in self.key_value_dict:
            self.key_value_dict[key] = self.key_value_dict[key] + list_of_values
        else: 
            self.key_value_dict[key] = list_of_values
    
    
    def get_list_of_all_keys(self):
        """This function gets entire key set if it has a finite set
        """
        
        if len(self.list_of_keys) == 0:
            return None
        else:
            return self.list_of_keys
        
    